Análisis de Incidencia Delictiva Municipial por tipo de Delito 2015-2024 y el desarrollo de un modelo predictivo

Autor: Diego González Farias

El siguiente proyecto de investigación se basa en los datos obtenidos en Datos Abiertos Gobierno de México con la finalidad de realizar un modelo que pueda predecir la cantidad de delitos que ocurrirán por entidad, así como su tipo de delito, basado en datos históricos de delincuencia municipal mensual de 2015 a octubre de 2024.

Análisis exploratorio de Datos¶

Carga de datos y transformacion¶

De manera inicial se busca el conocer los datos con los que se trabajara para poder visualizar datos estadísticos descriptivos, tendencias, relaciones e identificar las principales variables que se usaran en la creación del modelo predictivo.

In [2]:
#Importo librerias inicialmente para EDA o Exploratory Data Analysis
import pandas as pd
import matplotlib.pyplot as plt
from IPython.display import display
In [3]:
#Extraemos el archivo

data_delic = pd.read_csv("IDM_NM_oct24.csv", encoding='latin-1')  # Prueba con 'latin-1'
In [4]:
#Visualizamos las primeras filas del archivo
data_delic.head()
Out[4]:
Año Clave_Ent Entidad Cve. Municipio Municipio Bien jurídico afectado Tipo de delito Subtipo de delito Modalidad Enero ... Marzo Abril Mayo Junio Julio Agosto Septiembre Octubre Noviembre Diciembre
0 2015 1 Aguascalientes 1001 Aguascalientes La vida y la Integridad corporal Homicidio Homicidio doloso Con arma de fuego 2 ... 1 1 0 1 1 0 2 1 0.0 1.0
1 2015 1 Aguascalientes 1001 Aguascalientes La vida y la Integridad corporal Homicidio Homicidio doloso Con arma blanca 1 ... 0 0 0 1 0 1 0 0 0.0 0.0
2 2015 1 Aguascalientes 1001 Aguascalientes La vida y la Integridad corporal Homicidio Homicidio doloso Con otro elemento 0 ... 1 1 3 2 0 1 2 0 0.0 0.0
3 2015 1 Aguascalientes 1001 Aguascalientes La vida y la Integridad corporal Homicidio Homicidio doloso No especificado 1 ... 0 1 0 0 0 0 0 0 0.0 0.0
4 2015 1 Aguascalientes 1001 Aguascalientes La vida y la Integridad corporal Homicidio Homicidio culposo Con arma de fuego 0 ... 0 0 1 0 0 0 0 0 0.0 0.0

5 rows × 21 columns

In [5]:
data_delic.shape
Out[5]:
(2319072, 21)
In [6]:
#Columnas de las cuales se compone el archivo
data_delic.columns
Out[6]:
Index(['Año', 'Clave_Ent', 'Entidad', 'Cve. Municipio', 'Municipio',
       'Bien jurídico afectado', 'Tipo de delito', 'Subtipo de delito',
       'Modalidad', 'Enero', 'Febrero', 'Marzo', 'Abril', 'Mayo', 'Junio',
       'Julio', 'Agosto', 'Septiembre', 'Octubre', 'Noviembre', 'Diciembre'],
      dtype='object')
In [7]:
#Información de como esta compuesto el archivo
data_delic.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 2319072 entries, 0 to 2319071
Data columns (total 21 columns):
 #   Column                  Dtype  
---  ------                  -----  
 0   Año                     int64  
 1   Clave_Ent               int64  
 2   Entidad                 object 
 3   Cve. Municipio          int64  
 4   Municipio               object 
 5   Bien jurídico afectado  object 
 6   Tipo de delito          object 
 7   Subtipo de delito       object 
 8   Modalidad               object 
 9   Enero                   int64  
 10  Febrero                 int64  
 11  Marzo                   int64  
 12  Abril                   int64  
 13  Mayo                    int64  
 14  Junio                   int64  
 15  Julio                   int64  
 16  Agosto                  int64  
 17  Septiembre              int64  
 18  Octubre                 int64  
 19  Noviembre               float64
 20  Diciembre               float64
dtypes: float64(2), int64(13), object(6)
memory usage: 371.6+ MB
In [8]:
#Identificar los años incluidos
data_delic["Año"].unique()
Out[8]:
array([2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022, 2023, 2024],
      dtype=int64)

El archivo incluye los meses en cada columna con un formato ancho ("wide"), sin embargo para realizar el análisis de datos es conveniente tener los meses como filas para poder crear visualizaciones y modelado.

In [9]:
#Se realiza una división por Mes y se agrega una columna que muestra la "Cantidad" de delitos según cada modalidad

data_delic_long = pd.melt(
    data_delic,
    id_vars=[
        "Año", "Clave_Ent", "Entidad", "Cve. Municipio", "Municipio",
        "Bien jurídico afectado", "Tipo de delito", "Subtipo de delito", "Modalidad"
    ],
    value_vars=[
        "Enero", "Febrero", "Marzo", "Abril", "Mayo", "Junio", "Julio", 
        "Agosto", "Septiembre", "Octubre", "Noviembre", "Diciembre"
    ],
    var_name="Mes",
    value_name="Cantidad"
)
In [10]:
# Se realizara un "mapeo" para poder representar los meses de manera numerica
meses = {
    "Enero": 1, "Febrero": 2, "Marzo": 3, "Abril": 4, "Mayo": 5, "Junio": 6,
    "Julio": 7, "Agosto": 8, "Septiembre": 9, "Octubre": 10, "Noviembre": 11, "Diciembre": 12
}

# Mapear los nombres de los meses a números
data_delic_long["Mes"] = data_delic_long["Mes"].map(meses)
In [11]:
data_delic_long.head()
Out[11]:
Año Clave_Ent Entidad Cve. Municipio Municipio Bien jurídico afectado Tipo de delito Subtipo de delito Modalidad Mes Cantidad
0 2015 1 Aguascalientes 1001 Aguascalientes La vida y la Integridad corporal Homicidio Homicidio doloso Con arma de fuego 1 2.0
1 2015 1 Aguascalientes 1001 Aguascalientes La vida y la Integridad corporal Homicidio Homicidio doloso Con arma blanca 1 1.0
2 2015 1 Aguascalientes 1001 Aguascalientes La vida y la Integridad corporal Homicidio Homicidio doloso Con otro elemento 1 0.0
3 2015 1 Aguascalientes 1001 Aguascalientes La vida y la Integridad corporal Homicidio Homicidio doloso No especificado 1 1.0
4 2015 1 Aguascalientes 1001 Aguascalientes La vida y la Integridad corporal Homicidio Homicidio culposo Con arma de fuego 1 0.0
In [12]:
data_delic_long.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 27828864 entries, 0 to 27828863
Data columns (total 11 columns):
 #   Column                  Dtype  
---  ------                  -----  
 0   Año                     int64  
 1   Clave_Ent               int64  
 2   Entidad                 object 
 3   Cve. Municipio          int64  
 4   Municipio               object 
 5   Bien jurídico afectado  object 
 6   Tipo de delito          object 
 7   Subtipo de delito       object 
 8   Modalidad               object 
 9   Mes                     int64  
 10  Cantidad                float64
dtypes: float64(1), int64(4), object(6)
memory usage: 2.3+ GB
In [13]:
data_delic_long.describe()
Out[13]:
Año Clave_Ent Cve. Municipio Mes Cantidad
count 2.782886e+07 2.782886e+07 2.782886e+07 2.782886e+07 2.734220e+07
mean 2.019688e+03 1.920339e+01 1.930702e+04 6.500000e+00 7.094455e-01
std 2.802761e+00 7.523347e+00 7.539501e+03 3.452053e+00 8.386157e+00
min 2.015000e+03 1.000000e+00 1.001000e+03 1.000000e+00 -1.000000e+00
25% 2.017000e+03 1.400000e+01 1.405400e+04 3.750000e+00 0.000000e+00
50% 2.020000e+03 2.000000e+01 2.021600e+04 6.500000e+00 0.000000e+00
75% 2.022000e+03 2.400000e+01 2.405700e+04 9.250000e+00 0.000000e+00
max 2.024000e+03 3.200000e+01 3.205800e+04 1.200000e+01 2.143000e+03
In [14]:
#El formato ancho constaba de 21 columnas y 2,319,072 filas 
data_delic.shape
Out[14]:
(2319072, 21)
In [15]:
#El formato "largo" a usarse consta de 11 columnas y 278,288,864 filas 
data_delic_long.shape
Out[15]:
(27828864, 11)

La transformación realizada de formato "ancho" a "largo" redujo el numero de columnas en -10 pero realizo un aumento de filas en 276 millones aprox.

Dado que estamos trabajando con datos temporales se crea la columna "Fecha" para representar el tiempo en que sucede cada fila. El día no se encuentra presente en el set de datos por lo que se pondra por default el primero de cada mes.

In [16]:
data_delic_long["Fecha"] = pd.to_datetime(
    data_delic_long["Año"].astype(str) + '-' + data_delic_long["Mes"].astype(str) + '-01',
    format='%Y-%m-%d',  # Formato año-mes-día
    errors="coerce"  # Coerción de errores en caso de valores mal formados
)
In [17]:
#Existia un ruido visual en los datos al gráficar por lo que se filtra todas las fechas posterioes a octubre 2024
data_delic_long = data_delic_long[data_delic_long["Fecha"] <= "2024-10-31"]

Composición de los datos¶

Para conocer los datos vamos a comenzar respondiendo preguntas como ¿Cuantos tipos de delitos están registrados?, ¿Cuales son los delitos con más frecuencia? ,etc...

In [18]:
columnas_interes = [
    "Entidad",
    "Municipio",
    "Bien jurídico afectado",
    "Tipo de delito",
    "Subtipo de delito",
    "Modalidad"
]

conteo_unicos = data_delic_long[columnas_interes].nunique()

print(conteo_unicos)
Entidad                     32
Municipio                 2334
Bien jurídico afectado       7
Tipo de delito              40
Subtipo de delito           55
Modalidad                   59
dtype: int64

El registro histórico de delincuencia de Enero 2015 a Octubre 2024 esta compuesto por la información delictiva de 32 Estados, 2334 Municipios dividido por:

  • 40 Tipos de Delitos
  • 55 Subtipos de Delitos
  • 59 Modalidades
  • 7 Tipos de bienes afectados

¿Cuáles son los delitos más comunes?

In [19]:
# Top tipo de delito
top_tipos_delito = (
    data_delic_long.groupby("Tipo de delito")["Cantidad"]
    .sum()
    .sort_values(ascending=False)
    .head(10)
    .reset_index()
)

#Top subtipos de delito
top_subtipos_delito = (
    data_delic_long.groupby("Subtipo de delito")["Cantidad"]
    .sum()
    .sort_values(ascending=False)
    .head(10)
    .reset_index()
)

#Top por modalidad
top_modalidades = (
    data_delic_long.groupby("Modalidad")["Cantidad"]
    .sum()
    .sort_values(ascending=False)
    .head(10)
    .reset_index()
)

# Orden por bien afectado
top_bienes_afectados = (
    data_delic_long.groupby("Bien jurídico afectado")["Cantidad"]
    .sum()
    .sort_values(ascending=False)
    .reset_index()
)

# Formato a tabla para visualización
def formatear_tabla(data, nombre):
    print(f"\n--- {nombre} ---")
    return data.style.format({"Cantidad": "{:,.0f}"})


display(formatear_tabla(top_tipos_delito, "Top 10 Tipos de Delitos"))
display(formatear_tabla(top_subtipos_delito, "Top 10 Subtipos de Delitos"))
display(formatear_tabla(top_modalidades, "Top 10 Modalidades"))
display(formatear_tabla(top_bienes_afectados, "Top 10 Bienes Jurídicos Afectados"))
--- Top 10 Tipos de Delitos ---
  Tipo de delito Cantidad
0 Robo 6,578,412
1 Violencia familiar 2,107,776
2 Lesiones 2,028,612
3 Otros delitos del Fuero Común 1,830,298
4 Daño a la propiedad 1,316,460
5 Amenazas 1,045,332
6 Fraude 809,114
7 Narcomenudeo 649,390
8 Homicidio 405,704
9 Despojo 269,092
--- Top 10 Subtipos de Delitos ---
  Subtipo de delito Cantidad
0 Violencia familiar 2,107,776
1 Otros robos 1,926,104
2 Otros delitos del Fuero Común 1,830,298
3 Robo de vehículo automotor 1,622,678
4 Lesiones dolosas 1,529,661
5 Daño a la propiedad 1,316,460
6 Amenazas 1,045,332
7 Robo a negocio 908,250
8 Fraude 809,114
9 Robo a transeúnte en vía pública 724,174
--- Top 10 Modalidades ---
  Modalidad Cantidad
0 Sin violencia 3,227,551
1 Violencia familiar 2,107,776
2 Otros delitos del Fuero Común 1,830,298
3 Con violencia 1,712,684
4 Con otro elemento 1,366,771
5 Daño a la propiedad 1,316,460
6 Amenazas 1,045,332
7 Robo de coche de 4 ruedas Sin violencia 843,263
8 Fraude 809,114
9 Narcomenudeo 649,390
--- Top 10 Bienes Jurídicos Afectados ---
  Bien jurídico afectado Cantidad
0 El patrimonio 9,427,042
1 Otros bienes jurídicos afectados (del fuero común) 4,069,408
2 La vida y la Integridad corporal 2,538,472
3 La familia 2,502,983
4 La libertad y la seguridad sexual 567,671
5 Libertad personal 206,013
6 La sociedad 86,208

Tendencia en los ultimos años¶

Para un primer visual y conocer como se ha comportado el indice delictivo a lo largo de los ultimos años crearemos una linea de tiempo tomando en cuenta la suma de todos los tipos de delitos registrados.

In [20]:
import seaborn as sns


#Agrupamos los datos por Fecha y Cantidad de delitos
delitos_por_fecha = data_delic_long.groupby(["Fecha"])["Cantidad"].sum().reset_index()


plt.figure(figsize=(12, 6))
sns.lineplot(data=delitos_por_fecha, x="Fecha", y="Cantidad", marker="o")
plt.title("Cantidad de delitos a lo largo del tiempo")
plt.xlabel("Fecha")
plt.ylabel("Cantidad de delitos")
plt.xticks(rotation=45)
plt.tight_layout()
plt.show()

Observando el gráfico anterior podemos visualizar 3 puntos a simple vista:

  • 1- Una tendencia ascendente a lo largo del tiempo, es decir, los delitos van en aumento conforme pasa el tiempo.
  • 2- Una caida abrupta en los delitos a inicios de 2020, dicha caida es causada por la pandemia COVID-19
  • 3- Una estacionalidad en los años con un patron ciclico.

Top 10 tipos de delitos en los ultimos años

In [21]:
data_agrupada = (
    data_delic_long.groupby(["Fecha", "Tipo de delito"])["Cantidad"]
    .sum()
    .reset_index()
)


top_tipos_delito = (
    data_delic_long.groupby("Tipo de delito")["Cantidad"]
    .sum()
    .sort_values(ascending=False)
    .head(10)
    .index
)


data_top10 = data_agrupada[data_agrupada["Tipo de delito"].isin(top_tipos_delito)]


fig, axes = plt.subplots(5, 2, figsize=(20, 20))  # Grid de 5 filas x 2 columnas
axes = axes.flatten()  # Aplanar para iterar más fácilmente

for i, delito in enumerate(top_tipos_delito):
    ax = axes[i]
    # Filtrar los datos del delito actual
    data_delito = data_top10[data_top10["Tipo de delito"] == delito]
    # Graficar
    sns.lineplot(data=data_delito, x="Fecha", y="Cantidad", ax=ax, marker="o")
    ax.set_title(delito)
    ax.set_xlabel("Fecha")
    ax.set_ylabel("Cantidad")
    ax.tick_params(axis="x", rotation=45)  # Rotar etiquetas del eje X

# Ajustar diseño para evitar solapamiento
plt.tight_layout()
plt.show()

La tendencia al alza se puede observar en 9 de los 10 delitos más comunes registrados teniendo como la excepción "Robo" el cual es el delito con mayor numero de incidencias, a continuación se muestra un desglose de dicho delito según Subtipo de Delito y Modalidad.

In [22]:
#Se filtra el set de datos original por tipo de Delito "Robo"
data_robo=data_delic_long[data_delic_long["Tipo de delito"]=="Robo"]
In [23]:
data_robo.head()
Out[23]:
Año Clave_Ent Entidad Cve. Municipio Municipio Bien jurídico afectado Tipo de delito Subtipo de delito Modalidad Mes Cantidad Fecha
39 2015 1 Aguascalientes 1001 Aguascalientes El patrimonio Robo Robo a casa habitación Con violencia 1 4.0 2015-01-01
40 2015 1 Aguascalientes 1001 Aguascalientes El patrimonio Robo Robo a casa habitación Sin violencia 1 172.0 2015-01-01
41 2015 1 Aguascalientes 1001 Aguascalientes El patrimonio Robo Robo de vehículo automotor Robo de coche de 4 ruedas Con violencia 1 4.0 2015-01-01
42 2015 1 Aguascalientes 1001 Aguascalientes El patrimonio Robo Robo de vehículo automotor Robo de coche de 4 ruedas Sin violencia 1 114.0 2015-01-01
43 2015 1 Aguascalientes 1001 Aguascalientes El patrimonio Robo Robo de vehículo automotor Robo de motocicleta Con violencia 1 1.0 2015-01-01
In [24]:
robos_por_año=data_robo.groupby("Año")["Cantidad"].sum().reset_index
robos_por_año
Out[24]:
<bound method Series.reset_index of Año
2015    652647.0
2016    690048.0
2017    801099.0
2018    810592.0
2019    766096.0
2020    604275.0
2021    609271.0
2022    609635.0
2023    581510.0
2024    453239.0
Name: Cantidad, dtype: float64>
In [25]:
#Robos por subtipo
robos_por_subtipo=data_robo.groupby("Subtipo de delito")["Cantidad"].sum().reset_index()
robos_por_subtipo=robos_por_subtipo.sort_values(by="Cantidad",ascending=False)
robos_por_subtipo
Out[25]:
Subtipo de delito Cantidad
0 Otros robos 1926104.0
10 Robo de vehículo automotor 1622678.0
3 Robo a negocio 908250.0
5 Robo a transeúnte en vía pública 724174.0
1 Robo a casa habitación 715908.0
7 Robo de autopartes 173699.0
12 Robo en transporte público colectivo 142252.0
11 Robo en transporte individual 133829.0
6 Robo a transportista 95822.0
8 Robo de ganado 48466.0
4 Robo a transeúnte en espacio abierto al público 44493.0
13 Robo en transporte público individual 23715.0
9 Robo de maquinaria 15499.0
2 Robo a institución bancaria 3523.0
In [26]:
#Robos por Modalidad
robos_por_modalidad=data_robo.groupby("Modalidad")["Cantidad"].sum().reset_index()
robos_por_modalidad=robos_por_modalidad.sort_values(by="Cantidad",ascending=False)
robos_por_modalidad
Out[26]:
Modalidad Cantidad
13 Sin violencia 3227551.0
0 Con violencia 1712684.0
4 Robo de coche de 4 ruedas Sin violencia 843263.0
3 Robo de coche de 4 ruedas Con violencia 456859.0
10 Robo de motocicleta Sin violencia 233440.0
9 Robo de motocicleta Con violencia 88864.0
2 Robo de cables, tubos y otros objetos destinad... 9049.0
8 Robo de herramienta industrial o agrícola Sin ... 3791.0
7 Robo de herramienta industrial o agrícola Con ... 1094.0
12 Robo de tractores Sin violencia 685.0
11 Robo de tractores Con violencia 467.0
1 Robo de cables, tubos y otros objetos destinad... 413.0
6 Robo de embarcaciones pequeñas y grandes Sin v... 204.0
5 Robo de embarcaciones pequeñas y grandes Con v... 48.0

Cantidad de delitos por Estado

Es de interes también el conocer la distribución de delitos por Entidad, en el siguiente gráfico encontramos que en los ultimos años la cantidad de delitos el Estado de méxico y Ciudad de México conforman la mayor frecuencia en base a los delitos totales.

In [27]:
# Gráfico de la cantidad de delitos por entidad

delitos_por_entidad = data_delic_long.groupby(["Entidad"])["Cantidad"].sum().reset_index()

plt.figure(figsize=(14, 7))
sns.barplot(x="Entidad", y="Cantidad", data=delitos_por_entidad, estimator="sum", ci=None)
plt.title("Cantidad de Delitos por Entidad")
plt.xlabel("Entidad")
plt.ylabel("Cantidad de Delitos")
plt.xticks(rotation=90)
plt.tight_layout()
plt.show()
C:\Users\diego\AppData\Local\Temp\ipykernel_21608\3468840994.py:6: FutureWarning: 

The `ci` parameter is deprecated. Use `errorbar=None` for the same effect.

  sns.barplot(x="Entidad", y="Cantidad", data=delitos_por_entidad, estimator="sum", ci=None)
In [28]:
delitos_por_entidad
Out[28]:
Entidad Cantidad
0 Aguascalientes 345134.0
1 Baja California 1061823.0
2 Baja California Sur 217612.0
3 Campeche 81147.0
4 Chiapas 197919.0
5 Chihuahua 683165.0
6 Ciudad de México 2114668.0
7 Coahuila de Zaragoza 545071.0
8 Colima 226953.0
9 Durango 279362.0
10 Guanajuato 1266477.0
11 Guerrero 285579.0
12 Hidalgo 449537.0
13 Jalisco 1338937.0
14 Michoacán de Ocampo 423751.0
15 Morelos 439960.0
16 México 3517626.0
17 Nayarit 64072.0
18 Nuevo León 852284.0
19 Oaxaca 353692.0
20 Puebla 667387.0
21 Querétaro 525600.0
22 Quintana Roo 397245.0
23 San Luis Potosí 438688.0
24 Sinaloa 261502.0
25 Sonora 303098.0
26 Tabasco 512915.0
27 Tamaulipas 408414.0
28 Tlaxcala 52669.0
29 Veracruz de Ignacio de la Llave 714651.0
30 Yucatán 151902.0
31 Zacatecas 218957.0

Supuestos para el modelo¶

Estacionalidad¶

La estacionalidad en series de tiempo hace referencia a los patrones cíclicos que ocurren en intervalos regulares dentro de los datos, en este caso, en cada año parece observarse un ciclo que se repite en el aumento y disminución de los delitos. Estos patrones son importantes a considerar al momento de decidir que modelo predictivo se usará. En las gráficas de series de tiempo podemos observar una tendencia alcista así como un patron repetitivo donde a final de año existe una baja en los delitos y un alza repentina en los primeros meses del año. A continuación se realizarán pruebas estadísticas para comprobar si existe estacionalidad estadísticamente significativa en nuestros datos.

In [91]:
data_delic_long.info()
<class 'pandas.core.frame.DataFrame'>
DatetimeIndex: 27342196 entries, 2015-01-01 to 2023-12-01
Data columns (total 11 columns):
 #   Column                  Dtype  
---  ------                  -----  
 0   Año                     int64  
 1   Clave_Ent               int64  
 2   Entidad                 object 
 3   Cve. Municipio          int64  
 4   Municipio               object 
 5   Bien jurídico afectado  object 
 6   Tipo de delito          object 
 7   Subtipo de delito       object 
 8   Modalidad               object 
 9   Mes                     int32  
 10  Cantidad                float64
dtypes: float64(1), int32(1), int64(3), object(6)
memory usage: 2.3+ GB
In [90]:
import statsmodels.api as sm




# Establecer la 'Fecha' como índice de la serie temporal
data_delic_long.set_index('Fecha', inplace=True)
---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
~\AppData\Local\Temp\ipykernel_20856\1506702948.py in ?()
      3 
      4 
      5 
      6 # Establecer la 'Fecha' como índice de la serie temporal
----> 7 data_delic_long.set_index('Fecha', inplace=True)

~\anaconda3\lib\site-packages\pandas\core\frame.py in ?(self, keys, drop, append, inplace, verify_integrity)
   5855                     if not found:
   5856                         missing.append(col)
   5857 
   5858         if missing:
-> 5859             raise KeyError(f"None of {missing} are in the columns")
   5860 
   5861         if inplace:
   5862             frame = self

KeyError: "None of ['Fecha'] are in the columns"
In [ ]:
 

Prueba de Raíz Unitaria: Dickey-Fuller

En análisis de series de tiempo, una de las pruebas más comunes para evaluar la estacionariedad es la prueba de Dickey-Fuller aumentada (ADF). Esta prueba nos permite determinar si una serie de tiempo tiene una raíz unitaria, lo que implicaría que la serie es no estacionaria.

La prueba de Dickey-Fuller examina si una serie de tiempo tiene una raíz unitaria en su proceso generador. Si existe una raíz unitaria, los datos no serán estacionarios, lo que implica que la serie tiene una tendencia a largo plazo (ya sea creciente o decreciente).

Prueba de Dickey-Fuller La prueba de Dickey-Fuller es una regresión que sigue el siguiente modelo:

Δ 𝑦

𝑡¶

𝛼 + 𝛽 𝑡 + 𝛾 𝑦 𝑡 − 1 + ∑

𝑖¶

1 𝑝 𝜃 𝑖 Δ 𝑦 𝑡 − 𝑖 + 𝜖 𝑡 Δy t ​ =α+βt+γy t−1 ​

  • i=1 ∑ p ​ θ i ​ Δy t−i ​ +ϵ t ​

Donde:

Δ 𝑦

𝑡¶

𝑦 𝑡 − 𝑦 𝑡 − 1 Δy t ​ =y t ​ −y t−1 ​ es la diferencia de primer orden de la serie de tiempo. 𝑡 t es la tendencia lineal. 𝑦 𝑡 − 1 y t−1 ​ es el valor rezagado de la serie de tiempo. 𝜖 𝑡 ϵ t ​ es el término de error blanco. 𝛾 γ es el parámetro clave. Si

𝛾¶

0 γ=0, la serie tiene una raíz unitaria y es no estacionaria. Hipótesis de la Prueba Hipótesis nula ( 𝐻 0 H 0 ​ ): La serie de tiempo tiene una raíz unitaria (es no estacionaria).

Hipótesis alternativa ( 𝐻 1 H 1 ​ ): La serie de tiempo no tiene una raíz unitaria (es estacionaria).

Interpretación:

  • Valor p = 0.160944: Dado que el valor p es mayor que el umbral común de 0.05, no podemos rechazar la hipótesis nula. Esto significa que no hay suficiente evidencia para afirmar que la serie es estacionaria. Es decir, la serie de tiempo probablemente tenga una raíz unitaria y, por lo tanto, es no estacionaria.
In [70]:
from statsmodels.tsa.stattools import adfuller

# Prueba de Dickey-Fuller
result = adfuller(delitos_por_fecha['Cantidad'])
print(f'Valor p: {result[1]}')
Valor p: 0.16094478950584457
In [62]:
import plotly.express as px

# Agrupar por fecha y calcular la cantidad total de delitos
delitos_por_fecha = data_delic_long.groupby(["Fecha"])["Cantidad"].sum().reset_index()

# Crear el gráfico interactivo
fig = px.line(
    delitos_por_fecha,
    x="Fecha",
    y="Cantidad",
    title="Cantidad de delitos a lo largo del tiempo",
    labels={"Fecha": "Fecha", "Cantidad": "Cantidad de delitos"},
    markers=True,  # Para incluir los puntos en la línea
    template="plotly_white"  # Tema más limpio
)

# Mejorar las etiquetas de eje y formato de fechas
fig.update_xaxes(dtick="M1", tickformat="%b %Y", showgrid=True)  # Marcas mensuales
fig.update_yaxes(showgrid=True)
fig.update_traces(line=dict(color="blue"))  # Personalización del color de línea

# Mostrar el gráfico interactivo
fig.show()
In [59]:
# Agrupar por año, mes y calcular la cantidad total de delitos
delitos_por_fecha_mes = data_delic_long.groupby(["Mes"])["Cantidad"].sum().reset_index()

# Crear un gráfico de línea
plt.figure(figsize=(12, 6))
sns.lineplot(data=delitos_por_fecha, x="Mes", y="Cantidad", marker="o")
plt.title("Cantidad de delitos a lo largo del tiempo")
plt.xlabel("Mes")
plt.ylabel("Cantidad de delitos")
plt.xticks(rotation=45)
plt.tight_layout()
plt.show()
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
Cell In[59], line 6
      4 # Crear un gráfico de línea
      5 plt.figure(figsize=(12, 6))
----> 6 sns.lineplot(data=delitos_por_fecha, x="Mes", y="Cantidad", marker="o")
      7 plt.title("Cantidad de delitos a lo largo del tiempo")
      8 plt.xlabel("Mes")

File ~\anaconda3\lib\site-packages\seaborn\relational.py:618, in lineplot(data, x, y, hue, size, style, units, palette, hue_order, hue_norm, sizes, size_order, size_norm, dashes, markers, style_order, estimator, errorbar, n_boot, seed, orient, sort, err_style, err_kws, legend, ci, ax, **kwargs)
    615 errorbar = _deprecate_ci(errorbar, ci)
    617 variables = _LinePlotter.get_semantics(locals())
--> 618 p = _LinePlotter(
    619     data=data, variables=variables,
    620     estimator=estimator, n_boot=n_boot, seed=seed, errorbar=errorbar,
    621     sort=sort, orient=orient, err_style=err_style, err_kws=err_kws,
    622     legend=legend,
    623 )
    625 p.map_hue(palette=palette, order=hue_order, norm=hue_norm)
    626 p.map_size(sizes=sizes, order=size_order, norm=size_norm)

File ~\anaconda3\lib\site-packages\seaborn\relational.py:365, in _LinePlotter.__init__(self, data, variables, estimator, n_boot, seed, errorbar, sort, orient, err_style, err_kws, legend)
    351 def __init__(
    352     self, *,
    353     data=None, variables={},
   (...)
    359     # the kind of plot to draw, but for the time being we need to set
    360     # this information so the SizeMapping can use it
    361     self._default_size_range = (
    362         np.r_[.5, 2] * mpl.rcParams["lines.linewidth"]
    363     )
--> 365     super().__init__(data=data, variables=variables)
    367     self.estimator = estimator
    368     self.errorbar = errorbar

File ~\anaconda3\lib\site-packages\seaborn\_oldcore.py:640, in VectorPlotter.__init__(self, data, variables)
    635 # var_ordered is relevant only for categorical axis variables, and may
    636 # be better handled by an internal axis information object that tracks
    637 # such information and is set up by the scale_* methods. The analogous
    638 # information for numeric axes would be information about log scales.
    639 self._var_ordered = {"x": False, "y": False}  # alt., used DefaultDict
--> 640 self.assign_variables(data, variables)
    642 for var, cls in self._semantic_mappings.items():
    643 
    644     # Create the mapping function
    645     map_func = partial(cls.map, plotter=self)

File ~\anaconda3\lib\site-packages\seaborn\_oldcore.py:701, in VectorPlotter.assign_variables(self, data, variables)
    699 else:
    700     self.input_format = "long"
--> 701     plot_data, variables = self._assign_variables_longform(
    702         data, **variables,
    703     )
    705 self.plot_data = plot_data
    706 self.variables = variables

File ~\anaconda3\lib\site-packages\seaborn\_oldcore.py:938, in VectorPlotter._assign_variables_longform(self, data, **kwargs)
    933 elif isinstance(val, (str, bytes)):
    934 
    935     # This looks like a column name but we don't know what it means!
    937     err = f"Could not interpret value `{val}` for parameter `{key}`"
--> 938     raise ValueError(err)
    940 else:
    941 
    942     # Otherwise, assume the value is itself data
    943 
    944     # Raise when data object is present and a vector can't matched
    945     if isinstance(data, pd.DataFrame) and not isinstance(val, pd.Series):

ValueError: Could not interpret value `Mes` for parameter `x`
<Figure size 1200x600 with 0 Axes>
In [26]:
#Removeremos los meses que no tienen delitos por no estar incluidos, todo lo posterior a Octubre 

# Asegúrate de que las fechas sean del tipo datetime
fechas_a_eliminar = pd.to_datetime(["2024-11-01", "2024-12-01"])

# Filtrar los datos
delitos_por_fecha = delitos_por_fecha[~delitos_por_fecha["Fecha"].isin(fechas_a_eliminar)]
In [28]:
#Primeras visualizaciones
import seaborn as sns
import matplotlib.pyplot as plt

# Agrupar por año, mes y calcular la cantidad total de delitos
delitos_por_fecha = data_delic_long.groupby(["Fecha"])["Cantidad"].sum().reset_index()

# Crear un gráfico de línea
plt.figure(figsize=(12, 6))
sns.lineplot(data=delitos_por_fecha, x="Fecha", y="Cantidad", marker="o")
plt.title("Cantidad de delitos a lo largo del tiempo")
plt.xlabel("Fecha")
plt.ylabel("Cantidad de delitos")
plt.xticks(rotation=45)
plt.tight_layout()
plt.show()
In [43]:
delitos_por_fecha.columns
Out[43]:
Index(['Fecha', 'Cantidad'], dtype='object')
In [49]:
data_delic_long.filter("Año"==2024)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
~\AppData\Local\Temp\ipykernel_21028\3330384519.py in ?()
----> 1 data_delic_long.filter("Año"==2024)

~\anaconda3\lib\site-packages\pandas\core\generic.py in ?(self, items, like, regex, axis)
   5534         if items is not None:
   5535             name = self._get_axis_name(axis)
   5536             # error: Keywords must be strings
   5537             return self.reindex(  # type: ignore[misc]
-> 5538                 **{name: [r for r in items if r in labels]}  # type: ignore[arg-type]
   5539             )
   5540         elif like:
   5541 

TypeError: 'bool' object is not iterable
In [ ]:
 
In [42]:

C:\Users\diego\AppData\Local\Temp\ipykernel_21028\607123910.py:6: FutureWarning: 

The `ci` parameter is deprecated. Use `errorbar=None` for the same effect.

  sns.barplot(x="Entidad", y="Cantidad", data=delitos_por_entidad, estimator="sum", ci=None)
In [32]:
#Se agrega el shapefile de méxico, el cual se obtiene de la biblioteca digital de mapas https://www.inegi.org.mx/app/mapas/

import geopandas as gpd
import pandas as pd
import matplotlib.pyplot as plt
In [34]:
# Cargar el shapefile de México https://www.inegi.org.mx/app/mapas/
shapefile = "00ent.shp"
mexico_map = gpd.read_file(shapefile)
In [35]:
mexico_map.plot()
Out[35]:
<Axes: >
In [36]:
#A este punto tengo el dataset de delincuencia y el mapa de méxico por estado y sus datos geográficos
mexico_map.head()
Out[36]:
CVEGEO CVE_ENT NOMGEO geometry
0 01 01 Aguascalientes POLYGON ((2470517.824 1155028.588, 2470552.248...
1 02 02 Baja California MULTIPOLYGON (((1313480.513 1831458.607, 13135...
2 03 03 Baja California Sur MULTIPOLYGON (((1694656.344 1227647.637, 16946...
3 04 04 Campeche MULTIPOLYGON (((3544897.199 946994.621, 354491...
4 05 05 Coahuila de Zaragoza POLYGON ((2469954.193 1978522.993, 2469982.807...
In [37]:
delitos_por_estado = data_delic_long.groupby('Entidad')['Cantidad'].sum().reset_index()
In [38]:
data_delic_long['Cantidad'] = data_delic_long['Cantidad'].astype('float32')
In [39]:
data_delic_long.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 27828864 entries, 0 to 27828863
Data columns (total 12 columns):
 #   Column                  Dtype         
---  ------                  -----         
 0   Año                     int64         
 1   Clave_Ent               int64         
 2   Entidad                 object        
 3   Cve. Municipio          int64         
 4   Municipio               object        
 5   Bien jurídico afectado  object        
 6   Tipo de delito          object        
 7   Subtipo de delito       object        
 8   Modalidad               object        
 9   Mes                     int64         
 10  Cantidad                float32       
 11  Fecha                   datetime64[ns]
dtypes: datetime64[ns](1), float32(1), int64(4), object(6)
memory usage: 2.4+ GB
In [71]:
 
In [40]:
#Mapa combinado 
# Carga los shapefiles
entidades = gpd.read_file("00ent.shp")  # Límites de entidades
municipios = gpd.read_file("00mun.shp")  # Límites de municipios
lineas = gpd.read_file("00l.shp")  # Líneas (carreteras, ríos, etc.)
area_general = gpd.read_file("00a.shp")  # Área general

# Asegúrate de que todas las capas tengan la misma proyección (CRS)
entidades = entidades.to_crs("EPSG:4326")
municipios = municipios.to_crs(entidades.crs)
lineas = lineas.to_crs(entidades.crs)
area_general = area_general.to_crs(entidades.crs)

# Crear el mapa combinando las capas
fig, ax = plt.subplots(figsize=(12, 10))

# Dibuja el área general
area_general.plot(ax=ax, color="lightgray", edgecolor="black", alpha=0.5)

# Dibuja los límites de las entidades
entidades.plot(ax=ax, color="none", edgecolor="blue", linewidth=1)


# Configura el título y oculta los ejes
ax.set_title("Mapa combinado de México (INEGI)", fontsize=16)
ax.axis("off")

# Muestra el mapa
plt.tight_layout()
plt.show()
In [ ]:
 
In [146]:
#Creación de modelo de predicción de delitos por estado y fecha
In [162]:
data_delic_long.head()
Out[162]:
Año Clave_Ent Entidad Cve. Municipio Municipio Bien jurídico afectado Tipo de delito Subtipo de delito Modalidad Mes Cantidad Fecha
0 2015 1 Aguascalientes 1001 Aguascalientes La vida y la Integridad corporal Homicidio Homicidio doloso Con arma de fuego 1 2.0 2015-01-01
1 2015 1 Aguascalientes 1001 Aguascalientes La vida y la Integridad corporal Homicidio Homicidio doloso Con arma blanca 1 1.0 2015-01-01
2 2015 1 Aguascalientes 1001 Aguascalientes La vida y la Integridad corporal Homicidio Homicidio doloso Con otro elemento 1 0.0 2015-01-01
3 2015 1 Aguascalientes 1001 Aguascalientes La vida y la Integridad corporal Homicidio Homicidio doloso No especificado 1 1.0 2015-01-01
4 2015 1 Aguascalientes 1001 Aguascalientes La vida y la Integridad corporal Homicidio Homicidio culposo Con arma de fuego 1 0.0 2015-01-01
In [164]:
import xgboost as xgb
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_absolute_error
from sklearn.preprocessing import LabelEncoder

# Instanciamos el encoder
label_encoder = LabelEncoder()

# Codificamos las columnas categóricas
data_delic_long['Entidad'] = label_encoder.fit_transform(data_delic_long['Entidad'])
data_delic_long['Tipo de delito'] = label_encoder.fit_transform(data_delic_long['Tipo de delito'])

# Ver las primeras filas para asegurar la codificación
print(data_delic_long[['Entidad', 'Tipo de delito']].head())
   Entidad  Tipo de delito
0        0              18
1        0              18
2        0              18
3        0              18
4        0              18
In [166]:
# Definir las características y el objetivo
X = data_delic_long.drop(columns=['Cantidad', 'Fecha', 'Municipio', 'Bien jurídico afectado', 'Subtipo de delito', 'Modalidad'])  # Las columnas no relevantes
y = data_delic_long['Cantidad']
In [168]:
#División de conjunto de datos

from sklearn.model_selection import train_test_split

# Dividir el conjunto de datos
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

print(X_train.shape, X_test.shape)  # Ver las dimensiones de los conjuntos de entrenamiento y prueba
(22263091, 6) (5565773, 6)
In [170]:
#Entrenamiento
import xgboost as xgb
from sklearn.metrics import mean_squared_error

# Convertir a formato DMatrix que XGBoost entiende bien
dtrain = xgb.DMatrix(X_train, label=y_train)
dtest = xgb.DMatrix(X_test, label=y_test)

# Configuración de parámetros del modelo
params = {
    'objective': 'reg:squarederror',  # Regresión
    'max_depth': 6,
    'learning_rate': 0.1,
    'eval_metric': 'rmse',  # Usamos RMSE para evaluar
    'subsample': 0.8,  # Proporción de datos a usar en cada árbol
    'colsample_bytree': 0.8,  # Proporción de columnas
    'n_estimators': 100,  # Número de árboles
}

# Entrenar el modelo
model = xgb.train(params, dtrain, num_boost_round=100)

# Realizar predicciones en el conjunto de prueba
y_pred = model.predict(dtest)

# Evaluación del modelo
rmse = mean_squared_error(y_test, y_pred, squared=False)
print(f'RMSE: {rmse}')
C:\Users\diego2.gonzalez\AppData\Local\anaconda3\Lib\site-packages\xgboost\core.py:158: UserWarning: [16:18:49] WARNING: C:\buildkite-agent\builds\buildkite-windows-cpu-autoscaling-group-i-0c55ff5f71b100e98-1\xgboost\xgboost-ci-windows\src\learner.cc:740: 
Parameters: { "n_estimators" } are not used.

  warnings.warn(smsg, UserWarning)
RMSE: 7.586770534515381
C:\Users\diego2.gonzalez\AppData\Local\anaconda3\Lib\site-packages\sklearn\metrics\_regression.py:492: FutureWarning: 'squared' is deprecated in version 1.4 and will be removed in 1.6. To calculate the root mean squared error, use the function'root_mean_squared_error'.
  warnings.warn(
In [174]:
#Gráfica de predicción vs real
import matplotlib.pyplot as plt

# Gráfico de predicción vs valor real
plt.scatter(y_test, y_pred, alpha=0.3)
plt.plot([y_test.min(), y_test.max()], [y_test.min(), y_test.max()], '--r', label='Ideal')
plt.xlabel('Valor real')
plt.ylabel('Predicción')
plt.title('Predicción vs Real')
plt.legend()
plt.show()
C:\Users\diego2.gonzalez\AppData\Local\anaconda3\Lib\site-packages\IPython\core\pylabtools.py:170: UserWarning: Creating legend with loc="best" can be slow with large amounts of data.
  fig.canvas.print_figure(bytes_io, **kw)
In [176]:
#Gráfica de métricas

xgb.plot_importance(model, importance_type='weight', max_num_features=10, title='Top 10 Características Importantes')
plt.show()

# Visualizar las métricas durante el entrenamiento
evals_result = model.eval_set([(dtrain, 'train'), (dtest, 'test')])
print(evals_result)
[0]	train-rmse:7.80108055501440045	test-rmse:7.58676898050582516
In [178]:
#Redes neuronales recurrentes
from sklearn.preprocessing import MinMaxScaler

# Normalizar la variable 'Cantidad'
scaler = MinMaxScaler(feature_range=(0, 1))
data_delic_long['Cantidad_scaled'] = scaler.fit_transform(data_delic_long[['Cantidad']])
In [ ]:
import numpy as np

def create_sequences(df, sequence_length):
    sequences = []
    labels = []
    for i in range(len(df) - sequence_length):
        sequences.append(df.iloc[i:i+sequence_length, :-1].values)  # Datos de entrada
        labels.append(df.iloc[i+sequence_length, -1])  # Variable objetivo
    return np.array(sequences), np.array(labels)

# Usamos 6 meses anteriores para predecir el mes siguiente
sequence_length = 6
X, y = create_sequences(data_delic_long, sequence_length)
In [ ]:
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import LSTM, Dense, Dropout

# Definir el modelo LSTM
model = Sequential()

# Capa LSTM
model.add(LSTM(64, activation='relu', input_shape=(X.shape[1], X.shape[2]), return_sequences=False))

# Dropout para prevenir sobreajuste
model.add(Dropout(0.2))

# Capa densa de salida
model.add(Dense(1))  # Una sola salida (predicción de la cantidad)

# Compilar el modelo
model.compile(optimizer='adam', loss='mean_squared_error')
In [ ]:
# Dividir los datos en entrenamiento y prueba
train_size = int(len(X) * 0.8)  # 80% para entrenamiento
X_train, X_test = X[:train_size], X[train_size:]
y_train, y_test = y[:train_size], y[train_size:]

# Entrenar el modelo
history = model.fit(X_train, y_train, epochs=20, batch_size=64, validation_data=(X_test, y_test))
In [ ]:
import matplotlib.pyplot as plt

# Evaluar el modelo en los datos de prueba
y_pred = model.predict(X_test)

# Gráfico de predicciones vs reales
plt.plot(y_test, label='Real')
plt.plot(y_pred, label='Predicción')
plt.legend()
plt.title('Predicción vs Real')
plt.show()

# Calcular el RMSE
from sklearn.metrics import mean_squared_error
rmse = np.sqrt(mean_squared_error(y_test, y_pred))
print(f"RMSE: {rmse}")
In [ ]:
# Gráfica de la pérdida
plt.plot(history.history['loss'], label='Entrenamiento')
plt.plot(history.history['val_loss'], label='Validación')
plt.legend()
plt.title('Curva de pérdida')
plt.xlabel('Épocas')
plt.ylabel('Pérdida')
plt.show()
In [ ]:
 
In [89]:
##Creación de red neuronal con Transformer para predicción de delitos por estado
In [93]:
#Librerias
from sklearn.preprocessing import OneHotEncoder
from sklearn.model_selection import train_test_split
import pytorch_forecasting
from pytorch_forecasting import TemporalFusionTransformer
from pytorch_forecasting.data import TimeSeriesDataSet
from pytorch_forecasting.data import GroupNormalizer
In [134]:
# Asegúrate de que la columna 'Fecha' sea de tipo datetime
df['Fecha'] = pd.to_datetime(df['Fecha'])

# Convertir la fecha a un índice entero (por ejemplo, el número de meses desde el inicio del dataset)
df['time_idx'] = (df['Fecha'] - df['Fecha'].min()).dt.days // 30  # Índice de tiempo en meses

# Ahora 'time_idx' es un entero y puede ser utilizado como índice temporal
In [136]:
# Conversión de las variables categóricas
df['Entidad'] = df['Entidad'].astype('category')
df['Tipo de delito'] = df['Tipo de delito'].astype('category')

# Crear una columna para el mes/año para convertirlo en una característica temporal
df['Fecha'] = pd.to_datetime(df['Fecha'])
df['Mes'] = df['Fecha'].dt.month
df['Año'] = df['Fecha'].dt.year

# Agregar las columnas que usaremos para el modelo
df['Cantidad'] = df['Cantidad'].fillna(0)  # Rellenar los valores nulos de la cantidad de delitos

# Seleccionar las columnas que vamos a usar
df = df[['Entidad', 'Tipo de delito', 'Fecha', 'Cantidad', 'Mes', 'Año']]

# Codificación de las variables categóricas
encoder = OneHotEncoder(sparse_output=True)
encoded = encoder.fit_transform(df[['Entidad', 'Tipo de delito']])

# Convertir la matriz dispersa a DataFrame
encoded_df = pd.DataFrame.sparse.from_spmatrix(encoded, columns=encoder.get_feature_names_out(['Entidad', 'Tipo de delito']))
df = pd.concat([df, encoded_df], axis=1)

# Estructurar el dataset para temporal forecasting
max_prediction_length = 12  # Número de meses a predecir
max_encoder_length = 24  # Número de meses para el historial

# Crear el TimeSeriesDataSet
training = TimeSeriesDataSet(
    df,
    time_idx="Fecha",
    target="Cantidad",
    group_ids=["Entidad", "Tipo de delito"],  # Por cada combinación de entidad y tipo de delito
    min_encoder_length=max_encoder_length,
    max_encoder_length=max_encoder_length,
    min_prediction_length=1,
    max_prediction_length=max_prediction_length,
    static_categoricals=["Entidad", "Tipo de delito"],
    time_varying_known_categoricals=["Mes", "Año"],  # Mes y año como características
    time_varying_known_reals=["Fecha"],
    time_varying_unknown_reals=["Cantidad"],  # Variable de predicción
    add_relative_time_idx=True,
    add_target_scales=True,
    add_encoder_length=True
)

# Normalización de las características
scaler = GroupNormalizer(groups=["Entidad", "Tipo de delito"], transformation="soft")
training = training.scale(scaler)

# Dividir en entrenamiento y validación
train_data, val_data = training.split_after(0.8)

# Crear el modelo Temporal Fusion Transformer (TFT)
trainer = pytorch_forecasting.models.temporal_fusion_transformer.TemporalFusionTransformer.from_dataset(
    train_data,
    learning_rate=0.001,
    hidden_size=64,
    attention_head_size=4,
    dropout=0.1,
    hidden_continuous_size=32,
    output_size=1,
    loss=pytorch_forecasting.metrics.MeanAbsoluteError(),
    log_interval=10,
    reduce_on_plateau_patience=4,
    logging_metrics=[pytorch_forecasting.metrics.MeanSquaredError()],
    pl_trainer_kwargs={"accelerator": "gpu" if torch.cuda.is_available() else "cpu", "devices": 1}
)

# Ajuste del learning rate (opcional)
trainer.lr_find()

# Entrenar el modelo
trainer.fit(train_data, val_data, epochs=20, batch_size=64)

# Gráfico de Learning Rate
lr_finder = trainer.lr_find()
lr_finder.plot_lr_find()
plt.show()

# Evaluación del modelo en los datos de validación
best_model = trainer.load_best_model()
raw_predictions, x = best_model.predict(val_data, mode="raw", return_x=True)

# Predicción vs Real (Gráfico)
fig, ax = plt.subplots(figsize=(10, 6))
ax.plot(x["Fecha"], x["Cantidad"], label="Real", color='blue')
ax.plot(x["Fecha"], raw_predictions[:, 0], label="Predicción", color='red')
plt.title("Predicción vs Real (Cantidad de Delitos)")
plt.xlabel("Fecha")
plt.ylabel("Cantidad de Delitos")
plt.legend()
plt.show()
---------------------------------------------------------------------------
AssertionError                            Traceback (most recent call last)
Cell In[136], line 29
     26 max_encoder_length = 24  # Número de meses para el historial
     28 # Crear el TimeSeriesDataSet
---> 29 training = TimeSeriesDataSet(
     30     df,
     31     time_idx="Fecha",
     32     target="Cantidad",
     33     group_ids=["Entidad", "Tipo de delito"],  # Por cada combinación de entidad y tipo de delito
     34     min_encoder_length=max_encoder_length,
     35     max_encoder_length=max_encoder_length,
     36     min_prediction_length=1,
     37     max_prediction_length=max_prediction_length,
     38     static_categoricals=["Entidad", "Tipo de delito"],
     39     time_varying_known_categoricals=["Mes", "Año"],  # Mes y año como características
     40     time_varying_known_reals=["Fecha"],
     41     time_varying_unknown_reals=["Cantidad"],  # Variable de predicción
     42     add_relative_time_idx=True,
     43     add_target_scales=True,
     44     add_encoder_length=True
     45 )
     47 # Normalización de las características
     48 scaler = GroupNormalizer(groups=["Entidad", "Tipo de delito"], transformation="soft")

File ~\AppData\Local\anaconda3\Lib\site-packages\pytorch_forecasting\data\timeseries.py:351, in TimeSeriesDataSet.__init__(self, data, time_idx, target, group_ids, weight, max_encoder_length, min_encoder_length, min_prediction_idx, min_prediction_length, max_prediction_length, static_categoricals, static_reals, time_varying_known_categoricals, time_varying_known_reals, time_varying_unknown_categoricals, time_varying_unknown_reals, variable_groups, constant_fill_strategy, allow_missing_timesteps, lags, add_relative_time_idx, add_target_scales, add_encoder_length, target_normalizer, categorical_encoders, scalers, randomize_length, predict_mode)
    349 assert self.min_prediction_length > 0, "min prediction length must be larger than 0"
    350 assert isinstance(self.min_prediction_length, int), "min prediction length must be integer"
--> 351 assert data[time_idx].dtype.kind == "i", "Timeseries index should be of type integer"
    352 self.target = target
    353 self.weight = weight

AssertionError: Timeseries index should be of type integer
In [ ]:
# Preparar datos para entrenamiento con TemporalFusionTransformer

# Agrupar los datos por entidad y por mes para crear las secuencias temporales
max_encoder_length = 36  # Número de meses que el modelo usará para mirar atrás
max_prediction_length = 6  # El modelo predecirá la cantidad de delitos para los siguientes 6 meses

# Crear el dataset de series temporales
training = TimeSeriesDataSet(
    df,
    time_idx='Mes',  # Índice temporal, en este caso el mes
    target='Cantidad',  # Variable objetivo, cantidad de delitos
    group_ids=['Entidad'],  # Agrupamos por Entidad
    static_categoricals=['Entidad', 'Tipo de delito'],  # Variables estáticas categóricas
    static_reals=['Año'],  # Variables estáticas numéricas
    time_varying_known_categoricals=['Mes'],  # Variables conocidas que cambian con el tiempo
    time_varying_known_reals=['Año'],  # Variables numéricas conocidas que cambian con el tiempo
    time_varying_unknown_reals=['Cantidad'],  # Variable que cambia con el tiempo y es desconocida
    target_normalizer=GroupNormalizer(groups=['Entidad'], transformation='softplus'),  # Normalización
    max_encoder_length=max_encoder_length,
    max_prediction_length=max_prediction_length
)

# Dividir los datos en entrenamiento y validación
train_data, val_data = train_test_split(training, test_size=0.2, shuffle=False)
In [ ]:
# Definir el modelo
trainer = pytorch_forecasting.models.temporal_fusion_transformer.TemporalFusionTransformer.from_dataset(
    train_data,
    learning_rate=0.03,
    hidden_size=16,
    attention_head_size=4,
    dropout=0.1,
    hidden_continuous_size=8,
    output_size=1,  # Solo tenemos una salida, la cantidad de delitos
    loss=pytorch_forecasting.models.temporal_fusion_transformer.loss.MAE()
)

# Entrenar el modelo
trainer.fit(train_data, val_data, epochs=20, batch_size=64)

# Realizar predicciones
raw_predictions, x = trainer.predict(val_data, mode="raw", return_x=True)
In [ ]:
from sklearn.metrics import mean_absolute_error

# Extraer las predicciones reales y las predicciones
true_values = val_data['Cantidad'].values
predictions = raw_predictions['prediction'].values

# Evaluar el rendimiento
mae = mean_absolute_error(true_values, predictions)
print(f'Mean Absolute Error (MAE): {mae}')
In [ ]:
df = data_delic_long

# Conversión de las variables categóricas
df['Entidad'] = df['Entidad'].astype('category')
df['Tipo de delito'] = df['Tipo de delito'].astype('category')

# Crear una columna para el mes/año para convertirlo en una característica temporal
df['Fecha'] = pd.to_datetime(df['Fecha'])
df['Mes'] = df['Fecha'].dt.month
df['Año'] = df['Fecha'].dt.year

# Agregar las columnas que usaremos para el modelo
df['Cantidad'] = df['Cantidad'].fillna(0)  # Rellenar los valores nulos de la cantidad de delitos

# Crear un encoding de la variable 'Entidad' y 'Tipo de delito'
encoder = OneHotEncoder(sparse=False)
encoded = encoder.fit_transform(df[['Entidad', 'Tipo de delito']])

# Añadir las columnas codificadas a nuestro dataframe original
encoded_df = pd.DataFrame(encoded, columns=encoder.get_feature_names_out(['Entidad', 'Tipo de delito']))
df = pd.concat([df, encoded_df], axis=1)
In [ ]:
# Extraer las predicciones y los valores reales
true_values = val_data['Cantidad'].values
predictions = raw_predictions['prediction'].values

# Crear la gráfica
plt.figure(figsize=(12, 6))

# Graficar los valores reales
plt.plot(true_values, label='Valores Reales', color='blue', alpha=0.6)

# Graficar las predicciones
plt.plot(predictions, label='Predicciones', color='red', linestyle='--', alpha=0.6)

# Personalizar el gráfico
plt.title('Predicción vs Valor Real (Cantidad de Delitos)')
plt.xlabel('Índice')
plt.ylabel('Cantidad de Delitos')
plt.legend()

# Mostrar la gráfica
plt.show()
In [ ]:
 
In [ ]:
 
In [ ]:
 
In [ ]:
 
In [ ]:
 
In [ ]: